﻿Imports System.Reflection
Imports System.IO
Imports System.Windows.Forms

Public Class Spillskjerm

    ' Kropp, hode, arm, arm, ben, ben
    Const MAKS_TREKK As Integer = 6

    ' Ordet som skal gjettes kan være på maksimalt 15 bokstaver.
    Const MAKS_ORDLENGDE As Integer = 15

    ' Gyldige verdier er 0 til og med 2
    Public vanskelighetsgrad As Integer

    ' Ordet som spilleren forsøker å gjette
    Public ordet As String

    ' Oversikt over hvilke bokstaver som spilleren har gjettet så langt i spillet.
    ' Hvis en bokstav er forsøkt brukt, er tilhørende Boolean-verdi satt til True.
    ' Det er 29 variabler i matrisen: bruktbokstav(0) til og med bruktbokstav(28)
    Dim bruktBokstav(28) As Boolean

    ' Hvor mange trekk har spilleren brukt?
    ' Om denne når MAKS_TREKK har spilleren tapt.
    Dim trekkBrukt As Integer

    ' Bitmap-variabler brukes for å mellomlagre bildedata
    Dim bilde_bakgrunn As Bitmap
    Dim bilde_hode As Bitmap
    Dim bilde_arm_venstre As Bitmap
    Dim bilde_arm_hoyre As Bitmap
    Dim bilde_ben_venstre As Bitmap
    Dim bilde_ben_hoyre As Bitmap
    Dim bilde_kropp As Bitmap
    Dim bilde_hode_dodt As Bitmap

    ' g brukes for å tegne til spillvinduet, initialiseres i Init()
    Dim g As System.Drawing.Graphics

    ' For PCer der brukeren har skrudd opp skrift/element-størrelse må vi skalere innholdet i vinduet vårt tilsvarende.
    public skaleringsFaktorX, skaleringsFaktorY As Double

    ' Spillet kan på et gitt tidspunkt ha en av tre ulike tilstander, definert i SPILLSTATUS.
    Enum SPILLSTATUS
        NORMAL = 0
        VUNNET = 1
        TAPT = 2
    End Enum
    Dim status As SPILLSTATUS

    Enum SPILLMODUS
        ENSPILLER = 0
        TOSPILLER = 1
    End Enum
    Public modus As SPILLMODUS

    ' Farge på alfabetet før musepekeren holdes over
    Dim alfabetFarge1 As Color = Color.FromArgb(200, 100, 50)

    ' Farge på alfabetet når musepekeren holdes over
    Dim alfabetFarge2 As Color = Color.FromArgb(255, 150, 80)

    ' Brukes for å lese inn såkalte embedded resources (bildefiler og ordliste)
    Public _assembly As [Assembly]

    ' Spilleren ønsker å gå tilbake til startskjerm. Be om bekreftelse først.
    Private Sub btnStartskjerm_Click(sender As Object, e As EventArgs) Handles btnStartskjerm.Click

        ' Om spillstatus ikke er normal (spiller har tapt/vunnet), trenger vi ikke å be om bekreftelse for å avslutte spillet.
        If Not status = SPILLSTATUS.NORMAL Then
            TilbakeTilStart()
        Else
            ' Spør om spilleren ønsker å avslutte gjeldende spill
            If MsgBox("Er du sikker på at du vil avslutte gjeldende spill og gå tilbake til menyen?", MsgBoxStyle.OkCancel, "Tilbake til meny?") = MsgBoxResult.Ok Then
                TilbakeTilStart()
            End If
        End If

    End Sub

    ' Les inn filen ordliste.txt, og velg et passende ord i forhold til vanskelighetsgraden som er valgt.
    ' Denne funksjonen brukes kun i énspillermodus.
    Public Sub LesInnOrdliste()

        'Deklarerer variabler
        Dim ordListe As New List(Of String)

        Dim bokstaverOvre As Integer
        Dim bokstaverNedre As Integer

        'Setter øvre og nedre antall bokstaver basert på valgt vanskelighetsgrad.
        If vanskelighetsgrad = 0 Then
            bokstaverNedre = 2
            bokstaverOvre = 6
        ElseIf vanskelighetsgrad = 1 Then
            bokstaverNedre = 7
            bokstaverOvre = 10
        ElseIf vanskelighetsgrad = 2 Then
            bokstaverNedre = 11
            bokstaverOvre = 16
        End If

        ' Less inn ordliste-filen fra embedded resource
        Using _textStreamReader As New StreamReader(_assembly.GetManifestResourceStream("Hangman.ordliste.txt"))

            'Legger inn ordene fra ordListe.txt i listen
            Do Until _textStreamReader.EndOfStream
                ordet = _textStreamReader.ReadLine()
                If ordet.Length >= bokstaverNedre And ordet.Length <= bokstaverOvre And ordet.Length <= MAKS_ORDLENGDE Then

                    ' Om ordet som ble lest inn inneholder ugyldige tegn, hopp over og fortsett med neste ord.
                    For Each bokstav As Char In ordet
                        If BokstavTilTall(bokstav) = -1 Then
                            Continue Do
                        End If
                    Next

                    ordListe.Add(ordet)
                End If
            Loop
        End Using

        'Setter variabelen "ordet" til et tilfeldig ord som oppfyller kravene fra vanskelighetsgraden.
        ordet = ordListe(CInt((ordListe.Count) * Rnd()))

    End Sub

    ' Be spiller 1 om å skrive inn et ord som spiller 2 skal gjette.
    ' Denne funksjonen brukes kun i tospillermodus.
    ' Funksjonen returnerer "" om noe gikk galt, eller om spilleren trykte avbryt i inputboksen.
    Public Function BeOmOrd() As String

        Dim ord As String = InputBox("Spiller 1: Skriv inn et ord som spiller 2 skal gjette.", "Skriv inn et ord").ToLower()

        ' Om ordet som skrives inn inneholder ett eller flere tegn som ikke gjenkjennes som bokstavene a-å, gi tilbakemelding og returner blank streng.
        For Each bokstav As Char In ord
            If bokstav = " "c Then
                MsgBox("Ordet kan ikke inneholde mellomrom")
                Return ""
            ElseIf BokstavTilTall(bokstav) = -1 Then
                MsgBox("Ordet kan kun inneholde bokstaver i det norske alfabetet (a-å)")
                Return ""
            End If
        Next

        If ord.Length > MAKS_ORDLENGDE Then
            MsgBox("Ordet kan ikke være lengre enn " & MAKS_ORDLENGDE & " bokstaver.")
            Return ""
        End If

        Return ord

    End Function

    ' Nullstill alt av nødvendige spillvariabler, last inn data og vis spillvinduet.
    Public Sub Init()

        ' Last inn alle bildefiler i minnet.
        LastInnBilder()

        ' Vis spillskjerm og skjul startskjerm.
        Show()
        Startskjerm.Hide()

        ' Spillskjermen bør plasseres nøyaktig på samme plass som startskjermen, slik at ikke spillet "hopper" på skjermen.
        Me.Left = Startskjerm.Left
        Me.Top = Startskjerm.Top

        ' g brukes i Tegn() for å tegne til spillvinduet.
        g = Me.CreateGraphics()

        ' Nullstill bruktBokstav-matrisen. Ingen bokstaver er brukt enda.
        For i As Integer = 0 To 28
            bruktBokstav(i) = False
        Next

        ' Sett lblA til lblÅ til enabled
        For i As Integer = 0 To 28
            Dim labelNavn = "lbl" + TallTilBokstav(i)
            GetControlByName(labelNavn, Me).Enabled = True
        Next

        ' Nullstill trekkBrukt
        trekkBrukt = 0

        ' Nullstill vunnet/tapt-status
        status = SPILLSTATUS.NORMAL

        ' Oppdater skjermbilde (fjerner evt. kroppsdelbilder fra forrige spill)
        Me.Refresh()

    End Sub

    ' Les inn og lagre alle bildefiler til tilhørende Bitmap-variabler
    Private Sub LastInnBilder()

        'Lagrer bildene til tilhørende bitmap variabler. (Er usikker på om addresseringen virker her).

        Using _imageStream As Stream = _assembly.GetManifestResourceStream("Hangman.hode.bmp")
            bilde_hode = New Bitmap(_imageStream)
        End Using

        Using _imageStream As Stream = _assembly.GetManifestResourceStream("Hangman.arm_venstre.bmp")
            bilde_arm_venstre = New Bitmap(_imageStream)
        End Using

        Using _imageStream As Stream = _assembly.GetManifestResourceStream("Hangman.arm_hoyre.bmp")
            bilde_arm_hoyre = New Bitmap(_imageStream)
        End Using

        Using _imageStream As Stream = _assembly.GetManifestResourceStream("Hangman.fot_venstre.bmp")
            bilde_ben_venstre = New Bitmap(_imageStream)
        End Using

        Using _imageStream As Stream = _assembly.GetManifestResourceStream("Hangman.fot_hoyre.bmp")
            bilde_ben_hoyre = New Bitmap(_imageStream)
        End Using

        Using _imageStream As Stream = _assembly.GetManifestResourceStream("Hangman.kropp.bmp")
            bilde_kropp = New Bitmap(_imageStream)
        End Using

        Using _imageStream As Stream = _assembly.GetManifestResourceStream("Hangman.hode_dodt.bmp")
            bilde_hode_dodt = New Bitmap(_imageStream)
        End Using

        'Gjør hvit gjennomsiktig på bildene.
        bilde_hode.MakeTransparent(Color.White)
        bilde_arm_venstre.MakeTransparent(Color.White)
        bilde_arm_hoyre.MakeTransparent(Color.White)
        bilde_ben_venstre.MakeTransparent(Color.White)
        bilde_ben_hoyre.MakeTransparent(Color.White)
        bilde_kropp.MakeTransparent(Color.White)
        bilde_hode_dodt.MakeTransparent(Color.White)

    End Sub

    ' Tegn på nytt hele spillområdet, synliggjør eventuelle endringer.
    Private Sub Tegn()

        'Setter bildeboksene til å være lik kroppsdelene basert på hvor mange trekkBrukt.

        If trekkBrukt > 0 And trekkBrukt < 6 Then TegnBilde(bilde_hode, 17, 52)
        If trekkBrukt > 1 Then TegnBilde(bilde_kropp, 20, 55)
        If trekkBrukt > 2 Then TegnBilde(bilde_arm_venstre, 12, 80)
        If trekkBrukt > 3 Then TegnBilde(bilde_arm_hoyre, 83, 79)
        If trekkBrukt > 4 Then TegnBilde(bilde_ben_venstre, 20, 55)
        If trekkBrukt > 5 Then TegnBilde(bilde_ben_hoyre, 20, 55)
        If trekkBrukt > 5 Then TegnBilde(bilde_hode_dodt, 17, 52)

        ' Vis ordet på skjermen, men kun de bokstaver som spilleren allerede har gjettet.
        ' Bokstaver som ikke er gjettet vises som understrek.
        ' Dersom spilleren har tapt, vis hele ordet.

        Dim maskertOrd As String = ""

        For Each bokstav As Char In ordet.ToLower()
            If bruktBokstav(BokstavTilTall(bokstav)) Or status = SPILLSTATUS.TAPT Then
                maskertOrd += bokstav
            Else
                maskertOrd += "_"
            End If
            maskertOrd += " "
        Next

        lblOrdet.Text = maskertOrd

        lblTrekkIgjen.Text = "Trekk igjen: " + (MAKS_TREKK - trekkBrukt).ToString()

    End Sub

    ' Tegn bilde i spillvindu, ta hensyn til evt. DPI-innstilling i Windows (skrift/element-størrelse)
    Private Sub TegnBilde(ByRef bilde As Bitmap, x As Integer, y As Integer)
        g.DrawImage(bilde, CInt(x * skaleringsFaktorX), CInt(y * skaleringsFaktorY), CInt(bilde.Width * skaleringsFaktorX), CInt(bilde.Height * skaleringsFaktorY))
    End Sub

    ' Sjekk om spilleren har tapt eller vunnet, og håndter i så fall dette.
    ' Om ingen av delene er tilfelle, ikke gjør noe.
    Private Sub SjekkStatus()

        If trekkBrukt >= MAKS_TREKK Then

            ' Om spilleren har brukt like mange trekk som MAKS_TREKK har han tapt.
            Tap()

        Else

            ' Midlertidig variabel for å sjekke om spiller har gjettet hele ordet.
            Dim harGjettetHeleOrdet As Boolean = True

            ' Gå gjennom ordet som skal gjettes bokstav for bokstav
            For Each bokstav As Char In ordet.ToLower()

                ' Om en av bokstavene i ordet ikke er gjettet, betyr det at spilleren ikke har vunnet.
                If Not bruktBokstav(BokstavTilTall(bokstav)) Then
                    harGjettetHeleOrdet = False
                End If

            Next

            ' Vi fant ingen bokstaver i ordet som ikke var gjettet, ergo har spilleren gjettet hele ordet.
            If harGjettetHeleOrdet Then
                Vinn()
            End If

        End If

    End Sub

    ' Denne metoden kalles når spilleren har tapt. 
    Private Sub Tap()

        status = SPILLSTATUS.TAPT

        pnlMelding.Visible = True

        lblMelding.ForeColor = Color.Red
        lblMelding.Text = "Du har tapt."

        ' Tegn evt. endringer 
        Tegn()

    End Sub

    ' Denne metoden kalles når spilleren har vunnet.
    Private Sub Vinn()

        status = SPILLSTATUS.VUNNET

        pnlMelding.Visible = True

        lblMelding.ForeColor = Color.Green
        lblMelding.Text = "Du har vunnet."

        ' Tegn evt. endringer 
        Tegn()

    End Sub

    ' Avslutt gjeldende spill og gå tilbake til startskjermen.
    Private Sub TilbakeTilStart()

        ' Startskjermen bør plasseres nøyaktig på samme plass som spillskjermen, slik at ikke spillet "hopper" på skjermen.
        Startskjerm.Left = Me.Left
        Startskjerm.Top = Me.Top

        Startskjerm.Show()
        Hide()

        ' Skjul evt. melding slik at den ikke vises på neste spill.
        pnlMelding.Visible = False

    End Sub

    ' Spilleren har valgt bokstaven 'bok'. Test denne mot ordet som gjettes og sjekk hvordan det gikk.
    ' Bokstaven som skal testes skal sendes til funksjonen som en lower case ascii-karakter.
    Private Sub VelgBokstav(bok As Char)

        ' Om ordet som skal gjettes ikke inneholder bokstaven, betyr det at spilleren har brukt opp ett trekk.
        If Not ordet.ToLower().Contains(bok) Then
            trekkBrukt += 1
        End If

        ' Markér bokstaven som brukt.
        bruktBokstav(BokstavTilTall(bok)) = True

        ' Oppdater skjermbilde
        Tegn()

        ' Sjekk om spiller har vunnet/tapt
        SjekkStatus()

    End Sub

    ' Returnerer tallverdi som en bokstav representerer. a=0, b=1, c=2, ... , å=28
    ' Funksjonen returnerer -1 om bokstaven ikke gjenkjennes
    ' Speilfunksjon til TallTilBokstav(Integer)
    Public Function BokstavTilTall(bok As Char) As Integer

        ' Bokstavene æøå har en spesiell ascii-kode som avviker fra a-z, derfor må vi regne disse ut separat.
        If (bok = "æ"c) Then
            Return 26
        ElseIf (bok = "ø"c) Then
            Return 27
        ElseIf (bok = "å"c) Then
            Return 28
        ElseIf bok >= "a"c And bok <= "z"c Then
            ' Dersom valgt bokstav er i det vanlige engelske alfabetet, regn ut den numeriske forskjellen mellom valgt bokstav og 'a'.
            ' Om valgt bokstav er 'a' blir forskjellen 0. Om valgt bokstav er 'b' blir forskjellen 1. Og så videre.
            Return Asc(bok) - Asc("a"c)
        Else
            ' Bokstav ikke gjenkjent
            Return -1
        End If

    End Function

    ' Returnerer bokstav ut fra tilhørende tallverdi. a=0, b=1, c=2, ..., å=28
    ' Funksjonen returnerer '?' dersom  tallet ikke gjenkjennes.
    ' Speilfunksjon av BokstavTilTall(Char)
    Public Function TallTilBokstav(tall As Integer) As Char

        ' Bokstavene æøå har en spesiell ascii-kode som avviker fra a-z, derfor må vi regne disse ut separat.
        If tall >= 0 And tall <= 25 Then
            Return Chr(Asc("a"c) + tall)
        ElseIf tall = 26 Then
            Return "æ"c
        ElseIf tall = 27 Then
            Return "ø"c
        ElseIf tall = 28 Then
            Return "å"c
        Else
            ' Dette skal ikke skje!!
            MsgBox("Noe har gått galt i funksjonen TallTilBokstav(Integer)!! tall: " + tall.ToString()) ' DEBUG
            Return "?"
        End If

    End Function

    Private Sub Spillskjerm_FormClosed(sender As Object, e As FormClosedEventArgs) Handles MyBase.FormClosed

        ' Om spilleren lukker spillskjermen må vi unngå at startskjermen blir liggende å kjøre i bakgrunnen.
        ' Avslutt hele applikasjonen ved hjelp av 'End'.
        End

    End Sub

    ' Behandle museklikk på samtlige Labels (alfabetet)
    Private Sub Label_Clicked(sender As Label, e As EventArgs) Handles lblA.Click, lblB.Click, lblC.Click, lblD.Click,
                                                                        lblE.Click, lblF.Click, lblG.Click, lblH.Click,
                                                                        lblI.Click, lblJ.Click, lblK.Click, lblL.Click,
                                                                        lblM.Click, lblN.Click, lblO.Click, lblP.Click,
                                                                        lblO.Click, lblP.Click, lblQ.Click, lblR.Click,
                                                                        lblS.Click, lblT.Click, lblU.Click, lblV.Click,
                                                                        lblW.Click, lblX.Click, lblY.Click, lblZ.Click,
                                                                        lblÆ.Click, lblØ.Click, lblÅ.Click

        ' Behandle klikk kun om spillet fortsatt pågår.
        If Not status = SPILLSTATUS.NORMAL Then
            Return
        End If

        ' Deaktiver linken slik at samme bokstav ikke velges to ganger.
        sender.Enabled = False

        ' Finn ut hvilken bokstav som er valgt ved å lese innholdet i labelen som er klikket på.
        Dim valgtBokstav As Char = sender.Text.ToString().ToLower().First()

        ' I fall noen skulle klare å "dobbeltklikke" på en label, kontroller at selve bokstaven ikke er brukt tidligere.
        If Not bruktBokstav(BokstavTilTall(valgtBokstav)) Then
            ' Sjekk hvilken bokstav som står skrevet inne i LinkLabelen, konverter denne til lowercase Char og send den til VelgBokstav-funksjonen.
            VelgBokstav(valgtBokstav)
        End If

    End Sub

    Private Sub Label_MouseEnter(sender As Label, e As EventArgs) Handles lblA.MouseEnter, lblB.MouseEnter, lblC.MouseEnter, lblD.MouseEnter,
                                                                            lblE.MouseEnter, lblF.MouseEnter, lblG.MouseEnter, lblH.MouseEnter,
                                                                            lblI.MouseEnter, lblJ.MouseEnter, lblK.MouseEnter, lblL.MouseEnter,
                                                                            lblM.MouseEnter, lblN.MouseEnter, lblO.MouseEnter, lblP.MouseEnter,
                                                                            lblO.MouseEnter, lblP.MouseEnter, lblQ.MouseEnter, lblR.MouseEnter,
                                                                            lblS.MouseEnter, lblT.MouseEnter, lblU.MouseEnter, lblV.MouseEnter,
                                                                            lblW.MouseEnter, lblX.MouseEnter, lblY.MouseEnter, lblZ.MouseEnter,
                                                                            lblÆ.MouseEnter, lblØ.MouseEnter, lblÅ.MouseEnter

        sender.Font = New Font(sender.Font, FontStyle.Bold)
        sender.ForeColor = alfabetFarge2

    End Sub

    Private Sub Label_MouseLeave(sender As Label, e As EventArgs) Handles lblA.MouseLeave, lblB.MouseLeave, lblC.MouseLeave, lblD.MouseLeave,
                                                                            lblE.MouseLeave, lblF.MouseLeave, lblG.MouseLeave, lblH.MouseLeave,
                                                                            lblI.MouseLeave, lblJ.MouseLeave, lblK.MouseLeave, lblL.MouseLeave,
                                                                            lblM.MouseLeave, lblN.MouseLeave, lblO.MouseLeave, lblP.MouseLeave,
                                                                            lblO.MouseLeave, lblP.MouseLeave, lblQ.MouseLeave, lblR.MouseLeave,
                                                                            lblS.MouseLeave, lblT.MouseLeave, lblU.MouseLeave, lblV.MouseLeave,
                                                                            lblW.MouseLeave, lblX.MouseLeave, lblY.MouseLeave, lblZ.MouseLeave,
                                                                            lblÆ.MouseLeave, lblØ.MouseLeave, lblÅ.MouseLeave

        sender.Font = New Font(sender.Font, FontStyle.Regular)
        sender.ForeColor = alfabetFarge1

    End Sub

    Private Sub Spillskjerm_KeyPress(sender As Object, e As KeyPressEventArgs) Handles MyBase.KeyPress

        ' Behandle tastetrykk kun om spillet fortsatt pågår.
        If Not status = SPILLSTATUS.NORMAL Then
            Return
        End If

        ' Mellomlagre bokstaven som ble trykket og gjør den til lowercase.
        Dim bokstav As Char = e.KeyChar.ToString().ToLower()

        ' Sjekk om tasten som ble trykket er en gyldig bokstav a-å
        If Not BokstavTilTall(bokstav) = -1 Then

            ' Sjekk at bokstaven ikke er prøvd fra før av.
            If Not bruktBokstav(BokstavTilTall(bokstav)) Then

                VelgBokstav(bokstav)

                ' Deaktiver tilhørende "bokstav-knapp" i grensesnittet
                Dim lbl As Label = GetControlByName("lbl" + bokstav.ToString().ToUpper(), Me)
                lbl.Enabled = False

            End If

        End If

    End Sub

    ' Brukes for å "ta tak i" en grensesnittkomponent ved hjelp av navnet
    Public Shared Function GetControlByName(ByVal Name As String, ByVal CurrentForm As Form) As Control

        Dim info As System.Reflection.FieldInfo = CurrentForm.GetType().GetField("_" & Name, System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.Public Or System.Reflection.BindingFlags.IgnoreCase)

        If info Is Nothing Then
            Return Nothing
        Else
            Return CType(info.GetValue(CurrentForm), Control)
        End If

    End Function

    Private Sub Spillskjerm_Paint(sender As Object, e As PaintEventArgs) Handles MyBase.Paint

        ' Pass på at spillskjermen tegnes på nytt etter behov
        Tegn()

    End Sub

    Private Sub btnNyttSpill_Click(sender As Object, e As EventArgs) Handles btnNyttSpill.Click

        If modus = SPILLMODUS.ENSPILLER Then
            LesInnOrdliste()
            Init()
        ElseIf modus = SPILLMODUS.TOSPILLER Then

            ' Be spiller 1 om å skrive inn ordet som spiller 2 skal gjette.
            ordet = BeOmOrd()

            ' Hvis lengden på ordet er 0 betyr det at spiller 1 trykte Cancel, eller at han ikke skrev noe inn i inputboksen.
            ' I så fall, ikke start spillet.
            If ordet.Length = 0 Then
                Return
            End If

            ' Alt ser greit ut, start spillet.
            Init()
        End If

        pnlMelding.Visible = False

        ' Fjern fokus fra "Nytt spill"-knappen
        Me.Focus()

    End Sub

    Private Sub lblHjelp_MouseEnter(sender As Object, e As EventArgs) Handles lblHjelp.MouseEnter

        ' Vis instruksjonspanel når spilleren beveger pekeren over ?-knappen øverst til høyre
        lblHjelp.Visible = False
        pnlInstruksjon.Visible = True

    End Sub

    Private Sub pnlInstruksjon_MouseLeave(sender As Object, e As EventArgs) Handles pnlInstruksjon.MouseLeave

        ' Skjul instruksjonspanel når spilleren beveger pekeren utenfor panelet
        pnlInstruksjon.Visible = False
        lblHjelp.Visible = True

    End Sub

End Class